﻿<!DOCTYPE html>
<html>
<head>
  <title>Regrouping Demo</title>
  <!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
  <meta name="description" content="Create new collapsible groups, ungroup existing groups, and use drag-and-drop to change grouping memberships." />
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <script src="../release/go.js"></script>
  <script src="../assets/js/goSamples.js"></script>  <!-- this is only for the GoJS Samples framework -->
  <script id="code">
    function init() {
      if (window.goSamples) goSamples();  // init for these samples -- you don't need to call this
      var $ = go.GraphObject.make;

      myDiagram =
        $(go.Diagram, "myDiagramDiv",
          {
            // when a drag-drop occurs in the Diagram's background, make it a top-level node
            mouseDrop: function(e) { finishDrop(e, null); },
            layout:  // Diagram has simple horizontal layout
              $(go.GridLayout,
                { wrappingWidth: Infinity, alignment: go.GridLayout.Position, cellSize: new go.Size(1, 1) }),
            "commandHandler.archetypeGroupData": { isGroup: true, text: "Group", horiz: false },
            "undoManager.isEnabled": true
          });

      // The one template for Groups can be configured to be either layout out its members
      // horizontally or vertically, each with a different default color.

      function makeLayout(horiz) {  // a Binding conversion function
        if (horiz) {
          return $(go.GridLayout,
            {
              wrappingWidth: Infinity, alignment: go.GridLayout.Position,
              cellSize: new go.Size(1, 1), spacing: new go.Size(4, 4)
            });
        } else {
          return $(go.GridLayout,
            {
              wrappingColumn: 1, alignment: go.GridLayout.Position,
              cellSize: new go.Size(1, 1), spacing: new go.Size(4, 4)
            });
        }
      }

      function defaultColor(horiz) {  // a Binding conversion function
        return horiz ? "#FFDD33" : "#33D3E5";
      }

      function defaultFont(horiz) {  // a Binding conversion function
        return horiz ? "bold 18px sans-serif" : "bold 16px sans-serif";
      }

      // this function is used to highlight a Group that the selection may be dropped into
      function highlightGroup(e, grp, show) {
        if (!grp) return;
        e.handled = true;
        if (show) {
          // cannot depend on the grp.diagram.selection in the case of external drag-and-drops;
          // instead depend on the DraggingTool.draggedParts or .copiedParts
          var tool = grp.diagram.toolManager.draggingTool;
          var map = tool.draggedParts || tool.copiedParts;  // this is a Map
          // now we can check to see if the Group will accept membership of the dragged Parts
          if (grp.canAddMembers(map.toKeySet())) {
            grp.isHighlighted = true;
            return;
          }
        }
        grp.isHighlighted = false;
      }

      // Upon a drop onto a Group, we try to add the selection as members of the Group.
      // Upon a drop onto the background, or onto a top-level Node, make selection top-level.
      // If this is OK, we're done; otherwise we cancel the operation to rollback everything.
      function finishDrop(e, grp) {
        var ok = (grp !== null
          ? grp.addMembers(grp.diagram.selection, true)
          : e.diagram.commandHandler.addTopLevelParts(e.diagram.selection, true));
        if (!ok) e.diagram.currentTool.doCancel();
      }

      myDiagram.groupTemplate =
        $(go.Group, "Auto",
          {
            background: "transparent",
            ungroupable: true,
            // highlight when dragging into the Group
            mouseDragEnter: function(e, grp, prev) { highlightGroup(e, grp, true); },
            mouseDragLeave: function(e, grp, next) { highlightGroup(e, grp, false); },
            computesBoundsAfterDrag: true,
            // when the selection is dropped into a Group, add the selected Parts into that Group;
            // if it fails, cancel the tool, rolling back any changes
            mouseDrop: finishDrop,
            handlesDragDropForMembers: true,  // don't need to define handlers on member Nodes and Links
            // Groups containing Groups lay out their members horizontally
            layout: makeLayout(false)
          },
          new go.Binding("layout", "horiz", makeLayout),
          new go.Binding("background", "isHighlighted", function(h) {
            return h ? "rgba(255,0,0,0.2)" : "transparent";
          }).ofObject(),
          $(go.Shape, "Rectangle",
            { fill: null, stroke: defaultColor(false), strokeWidth: 2 },
            new go.Binding("stroke", "horiz", defaultColor),
            new go.Binding("stroke", "color")),
          $(go.Panel, "Vertical",  // title above Placeholder
            $(go.Panel, "Horizontal",  // button next to TextBlock
              { stretch: go.GraphObject.Horizontal, background: defaultColor(false) },
              new go.Binding("background", "horiz", defaultColor),
              new go.Binding("background", "color"),
              $("SubGraphExpanderButton",
                { alignment: go.Spot.Right, margin: 5 }),
              $(go.TextBlock,
                {
                  alignment: go.Spot.Left,
                  editable: true,
                  margin: 5,
                  font: defaultFont(false),
                  opacity: 0.75,  // allow some color to show through
                  stroke: "#404040"
                },
                new go.Binding("font", "horiz", defaultFont),
                new go.Binding("text", "text").makeTwoWay())
            ),  // end Horizontal Panel
            $(go.Placeholder,
              { padding: 5, alignment: go.Spot.TopLeft })
          )  // end Vertical Panel
        );

      myDiagram.nodeTemplate =
        $(go.Node, "Auto",
          { // dropping on a Node is the same as dropping on its containing Group, even if it's top-level
            mouseDrop: function(e, node) { finishDrop(e, node.containingGroup); }
          },
          $(go.Shape, "Rectangle",
            { fill: "#ACE600", stroke: null },
            new go.Binding("fill", "color")),
          $(go.TextBlock,
            {
              margin: 5,
              editable: true,
              font: "bold 13px sans-serif",
              opacity: 0.75,
              stroke: "#404040"
            },
            new go.Binding("text", "text").makeTwoWay())
        );

      // initialize the Palette and its contents
      myPalette =
        $(go.Palette, "myPaletteDiv",
          {
            nodeTemplateMap: myDiagram.nodeTemplateMap,
            groupTemplateMap: myDiagram.groupTemplateMap
          });

      myPalette.model = new go.GraphLinksModel([
        { text: "lightgreen", color: "#ACE600" },
        { text: "yellow", color: "#FFDD33" },
        { text: "lightblue", color: "#33D3E5" },
        { isGroup: true, text: "H Group", horiz: true },
        { isGroup: true, text: "V Group", horiz: false }
      ]);

      var slider = document.getElementById("levelSlider");
      slider.addEventListener('change', reexpand);
      slider.addEventListener('input', reexpand);

      load();
    }

    function reexpand(e) {
      myDiagram.commit(function(diag) {
        var level = parseInt(document.getElementById("levelSlider").value);
        diag.findTopLevelGroups().each(function(g) { expandGroups(g, 0, level); })
      } ,"reexpand");
    }
    function expandGroups(g, i, level) {
      if (!(g instanceof go.Group)) return;
      g.isSubGraphExpanded = i < level;
      g.memberParts.each(function(m) {
        expandGroups(m, i + 1, level);
      })
    }

    // save a model to and load a model from JSON text, displayed below the Diagram
    function save() {
      document.getElementById("mySavedModel").value = myDiagram.model.toJson();
      myDiagram.isModified = false;
    }
    function load() {
      myDiagram.model = go.Model.fromJson(document.getElementById("mySavedModel").value);
    }
  </script>
</head>
<body onload="init()">
<div id="sample">
  <div style="width: 100%; display: flex; justify-content: space-between">
    <div id="myPaletteDiv" style="width: 130px; margin-right: 2px; background-color: whitesmoke; border: solid 1px black"></div>
    <div id="myDiagramDiv" style="flex-grow: 1; height: 500px; border: solid 1px black"></div>
  </div>
  <p>
    This sample allows the user to drag nodes, including groups, into and out of groups,
    both from the Palette as well as from within the Diagram.
    See the <a href="../intro/groups.html">Groups Intro page</a> for an explanation of GoJS Groups.
  </p>
  <p>
    Highlighting to show feedback about potential addition to a group during a drag is implemented
    using <a>GraphObject.mouseDragEnter</a> and <a>GraphObject.mouseDragLeave</a> event handlers.
    Because <a>Group.computesBoundsAfterDrag</a> is true, the Group's <a>Placeholder</a>'s bounds are
    not computed until the drop happens, so the group does not continuously expand as the user drags
    a member of a group.
  </p>
  <p>
    When a drop occurs on a Group or a regular Node, the <a>GraphObject.mouseDrop</a> event handler
    adds the selection (the dragged Nodes) to the Group as new members.
    The <a>Diagram.mouseDrop</a> event handler changes the dragged selected Nodes to be top-level,
    rather than members of whatever Groups they had been in. 
  </p>
  <p>
    The slider controls how many nested levels of Groups are expanded. </br>
    Semantic zoom level: <input id="levelSlider" type="range" min="0" max="5" value="3" />
  </p>
  <div id="buttons">
    <button id="saveModel" onclick="save()">Save</button>
    <button id="loadModel" onclick="load()">Load</button>
    Diagram Model saved in JSON format:
  </div>
  <textarea id="mySavedModel" style="width:100%;height:300px">
{ "class": "go.GraphLinksModel",
  "nodeDataArray": [
{"key":1, "isGroup":true, "text":"Main 1", "horiz":true},
{"key":2, "isGroup":true, "text":"Main 2", "horiz":true},
{"key":3, "isGroup":true, "text":"Group A", "group":1},
{"key":4, "isGroup":true, "text":"Group B", "group":1},
{"key":5, "isGroup":true, "text":"Group C", "group":2},
{"key":6, "isGroup":true, "text":"Group D", "group":2},
{"key":7, "isGroup":true, "text":"Group E", "group":6},
{"text":"first A", "group":3, "key":-7},
{"text":"second A", "group":3, "key":-8},
{"text":"first B", "group":4, "key":-9},
{"text":"second B", "group":4, "key":-10},
{"text":"third B", "group":4, "key":-11},
{"text":"first C", "group":5, "key":-12},
{"text":"second C", "group":5, "key":-13},
{"text":"first D", "group":6, "key":-14},
{"text":"first E", "group":7, "key":-15}
 ],
  "linkDataArray": [  ]}
  </textarea>
</div>
</body>
</html>
